﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Linq.Expressions;

namespace GoodStuff.Data
{
    public interface IDataContext : IDisposable
    {
        Table<T> Select<T>() where T : class;
        bool Any<T>(Expression<Func<T, bool>> expression) where T : class;
        IQueryable<T> Where<T>(Expression<Func<T, bool>> expression) where T : class;
        T Single<T>(Expression<Func<T, bool>> expression) where T : class;
        T SingleOrDefault<T>(Expression<Func<T, bool>> expression) where T : class;
        void InsertOnSubmit<T>(T item) where T : class;
        void DeleteOnSubmit<T>(T item) where T : class;       
        void SubmitChanges();
        ITransactionalDataContext BeginTransaction();        
    }

    public interface ITransactionalDataContext : IDataContext
    {
        void Rollback();
        void Commit();
    }

    public interface IDataContextBuilder
    {
        IDataContextBuilder LoadWith<T>(Expression<Func<T, object>> expression);
        IDataContextBuilder AssociateWith<T>(Expression<Func<T, object>> expression);
        IDataContext Build();
        ITransactionalDataContext BeginTransaction();
        IDataContextBuilder EnableLogging();
        IDataContextBuilder EnableLogging(System.IO.TextWriter writer);
    }

    public interface IDataContextBuilderWithObjectTracking : IDataContextBuilder
    {
        IDataContextBuilder EnableObjectTracking();
        IDataContextBuilder EnableObjectTracking(bool enable);
    }

    public class DataContextBuilder : IDataContextBuilderWithObjectTracking
    {
        private DataLoadOptions _options;
        private bool _enableObjectTracking = false;
        private string _connectionString;
        private System.IO.TextWriter _log = null;
        
        public DataContextBuilder()
        {
             string connectionString = ConfigurationHelper.GetConnectionString();
             _connectionString = connectionString;
        }

        public DataContextBuilder(string connectionName)
        {
            string connectionString = ConfigurationHelper.GetConnectionString(connectionName);
            _connectionString = connectionString;
        }

        public IDataContext Build()
        {
            DataContext context = new DataContext(_connectionString);

            context.ObjectTrackingEnabled = _enableObjectTracking;
            context.LoadOptions = _options;
            context.Log = _log;
            return new DataContextWrapper(context);
        }

        public IDataContextBuilder EnableObjectTracking()
        {
            _enableObjectTracking = true;
            return this;
        }

        public IDataContextBuilder EnableObjectTracking(bool enable)
        {
            _enableObjectTracking = enable;
            return this;
        }

        public IDataContextBuilder EnableLogging()
        {
            _log = new Diagnostics.TraceTextWriter();
            return this;
        }

        public IDataContextBuilder EnableLogging(System.IO.TextWriter writer)
        {
            _log = writer;
            return this;
        }

        public IDataContextBuilder LoadWith<T>(Expression<Func<T, object>> expression)
        {
            if (_options == null)
            {
                _options = new DataLoadOptions();
            }
            _options.LoadWith(expression);
            return this;
        }

        public IDataContextBuilder AssociateWith<T>(Expression<Func<T, object>> expression)
        {
            if (_options == null)
            {
                _options = new DataLoadOptions();
            }
            _options.AssociateWith(expression);
            return this;
        }   
           
        private class DataContextWrapper : ITransactionalDataContext
        {
            private DataContext _context;
            private System.Data.Common.DbTransaction _currentTransaction;
            
            public DataContextWrapper(DataContext context)
            {
                _context = context;
            }                     

            public Table<T> Select<T>() where T : class
            {
                return _context.GetTable<T>();
            }

            /// <summary>
            /// Shorthand for .Select.Where.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="expression"></param>
            /// <returns></returns>
            public IQueryable<T> Where<T>(Expression<Func<T, bool>> expression) where T : class
            {
                return Select<T>().Where(expression);
            }

            /// <summary>
            /// Shorthand for .Select.Any.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="expression"></param>
            /// <returns></returns>
            public bool Any<T>(Expression<Func<T, bool>> expression) where T : class
            {
                return Select<T>().Any(expression);
            }

            /// <summary>
            /// Shorthand for .Select.Single.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="expression"></param>
            /// <returns></returns>
            public T Single<T>(Expression<Func<T, bool>> expression) where T : class
            {
                return Select<T>().Single(expression);
            }

            /// <summary>
            /// Shorthand for .Select.SingleOrDefault.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="expression"></param>
            /// <returns></returns>
            public T SingleOrDefault<T>(Expression<Func<T, bool>> expression) where T : class
            {
                return Select<T>().SingleOrDefault(expression);
            }

            public void InsertOnSubmit<T>(T item) where T : class
            {
                Select<T>().InsertOnSubmit(item);
            }

            public void SubmitChanges()
            {
                _context.SubmitChanges();
            }

            public void Dispose()
            {
                if (_context != null)
                {
                    if (_context.Connection.State != System.Data.ConnectionState.Closed)
                    {
                        _context.Connection.Close();
                        _context.Connection.Dispose();
                    }

                    _context.Dispose();
                    _context = null;
                }
            }

            public ITransactionalDataContext BeginTransaction()
            {
                if (_context.Connection.State != System.Data.ConnectionState.Open)
                {
                    _context.Connection.Open();
                }
                _currentTransaction = _context.Connection.BeginTransaction();
                _context.Transaction = _currentTransaction;

                return this;
            }

            public void Rollback()
            {
                _currentTransaction.Rollback();
                _currentTransaction = null;
            }

            public void Commit()
            {
                _currentTransaction.Commit();
                _currentTransaction = null;
            }

            public void DeleteOnSubmit<T>(T item) where T : class
            {
                Select<T>().DeleteOnSubmit(item);
            }        
        }

        public ITransactionalDataContext BeginTransaction()
        {
            return Build().BeginTransaction();
        }
    }
}
